iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0
Modern Web

react 學習記錄系列 第 17

[Day17]我的 react 學習記錄 - custom hook

  • 分享至 

  • xImage
  •  

這篇文章的主要內容

簡單介紹什麼是 custom Hook。


Custom Hook

目前為止都是切分畫面把畫面上的內容拆成小元件做處理,在 react 裡面可以透過創建自己的 custom hook 來讓邏輯共用,比如 input 的 handler 或是 fetch api 等。

注意事項

  • 命名需以 use開頭 ( 要小寫開頭跟其他 react hooks 一樣 )。
  • 跟其他的 react hook 一樣只能在元件裡使用,且只能在元件的最外層使用,不能放在迴圈或是判斷式裡,如果有需要可以建立一個新的子元件,放在子元件裡。

useInput - input handler

假設我有兩個 input 要管理,可能會使用兩個 useState 跟 handle function 作處理。

function App() {
  const [firstName, setFirstName] = useState<string>("Evan");
  const [lastName, setLastName] = useState<string>("Hsu");

  function handleFirstNameChange({
    target,
  }: React.ChangeEvent<HTMLInputElement>) {
    setFirstName(target.value);
  }

  function handleLastNameChange({
    target,
  }: React.ChangeEvent<HTMLInputElement>) {
    setLastName(target.value);
  }

  return (
    <div>
      <label>
        First name:
        <input value={firstName} onChange={handleFirstNameChange} />
      </label>
      <br />
      <label>
        Last name:
        <input value={lastName} onChange={handleLastNameChange} />
      </label>

      <p>
        <b>
          Good morning, {firstName} {lastName}.
        </b>
      </p>
    </div>
  );
}

可以看到元件裡面有兩個重複的 useState 跟 handler function,所以我們可以嘗試把重複的地方拆出來,製作管理 input 的 custom hook。

function useInput(initValue: string) {
  const [value, setValue] = useState(initValue);

  function handleChange({ target }: React.ChangeEvent<HTMLInputElement>) {
    setValue(target.value);
  }

  return {
    value: value,
    onChange: handleChange,
  };
}

這樣我們就把管理 input 的 change 跟 state 封裝在這一個 useInput 裡面了,要使用的時候就可以這樣用。

function App() {
  const firstNameProps = useInput("Evan");
  const lastNameProps = useInput("Hsu");

  return (
    <div>
      <label>
        First name:
        <input {...firstNameProps} />
      </label>
      <br />
      <label>
        Last name:
        <input {...lastNameProps} />
      </label>
      <p>
        <b>
          Good morning, {firstNameProps.value} {lastNameProps.value}.
        </b>
      </p>
    </div>
  );
}

簡化過後的 App 元件裡面也變得更加簡潔,好閱讀跟管理。


useFetchUrl - fetch

除了 Input 以外常常被封裝成 custom hook 的還有 fetch 的行為。

function useFetchUrl(url: string) {
  const [data, setData] = useState([]);
  const [status, setStatus] = useState("");
  useEffect(() => {
    setStatus("Loading");
    let ignore = false;
    setData([]);
    fetch(url)
      .then((result) => result.json())
      .then((data) => {
        if (!ignore) {
          setStatus("Success");
          setData(data);
        }
      })
      .catch(() => {
        setStatus("Error");
      });
    return () => {
      ignore = true;
    };
  }, [url]);

  return { data, status };
}

這個 useFetchUrl 的 custom hook 除了回傳 fetch 取得的資料以外,也會回傳當前 fetch 的狀態,在元件使用的時候可以依照不同的狀態做不同的處理,如下。

function App() {
  const { data, status } = useFetchUrl("https://api.....");

  return (
    <div>
      {status === "Loading" && <p>Loading....</p>}
      {status === "Error" && <p>Something went wrong....</p>}
      {status === "Success" && data.map((item) => <p>{item}</p>)}
    </div>
  );
}

這樣就可以在不同的地方共用這個 useFetchUrl 的 custom hook,讓元件裡面的程式碼更簡潔。


useClickOutside

還有另外一個常見的 custom hook,就是 useClickOutside,在瀏覽網頁時,常常會跳出提示或互動用的彈窗,通常可以透過點擊彈窗外的範圍把彈窗關閉,這也可以寫成一個 custom hook。

const useClickOutSide = (callback: () => void) => {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handleClick = (event: MouseEvent) => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        callback();
      }
    };
    document.addEventListener("click", handleClick, true);

    return () => document.removeEventListener("click", handleClick, true);
  }, [ref, callback]);
  return ref;
};

這個 custom hook 接收一個 function 當點擊指定 DOM 節點外面的時候要進行什麼行為,然後會 return 一個 ref,讓使用這個 hook 的元件可以把 ref 放在希望作用的 DOM 節點上。

function Dialog({ close }: { close: () => void }) {
  const ref = useClickOutSide(close);
  return (
    <div className="dialog">
      <div ref={ref}>
        <p>Hello!!!!</p>
        <button onClick={close}>Close</button>
      </div>
    </div>
  );
}

這邊我把 ref 放在了 Dialog 的 DOM 節點上,而 close function 則是從外面傳進來的 關閉 dialog 的 function。

function App() {
  const [open, setOpen] = useState(false);

  const handleOpenDialog = () => setOpen(true);
  const handleCloseDialog = () => setOpen(false);
  return (
    <div>
      <button onClick={handleOpenDialog}>Open</button>
      {open && <Dialog close={handleCloseDialog} />}
    </div>
  );
}

實際看起來就會是這樣。

customhook-1

當我點擊 close 按鈕或是 dialog 的外面時元件就會被關閉。


Reusing Logic with Custom Hooks - react document

下一篇簡單介紹如何透過 Render Props 的方法讓父元件決定子元件要顯示的內容
如果內容有誤再麻煩大家指教,我會盡快修改。

這個系列的文章會同步更新在我個人的 Medium,歡迎大家來看看 👋👋👋
Medium


上一篇
[Day16]我的 react 學習記錄 - useLayoutEffect
下一篇
[Day18]我的 react 學習記錄 - Render Props
系列文
react 學習記錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言